import Vue, { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';

import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import SecurityTrainingPromoBanner from 'ee/security_dashboard/components/project/security_training_promo_banner.vue';
import Cookies from '~/lib/utils/cookies';
import ProjectVulnerabilityReport from 'ee/security_dashboard/components/project/project_vulnerability_report.vue';
import ReportNotConfiguredProject from 'ee/security_dashboard/components/project/report_not_configured_project.vue';
import VulnerabilityReportTabs from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tabs.vue';
import VulnerabilityReportTab from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_report_tab.vue';
import ProjectPipelineStatus from 'ee/security_dashboard/components/shared/project_pipeline_status.vue';
import SecurityScannerAlert from 'ee/security_dashboard/components/project/security_scanner_alert.vue';
import securityScannersQuery from 'ee/security_dashboard/graphql/queries/project_security_scanners.query.graphql';
import AutoFixUserCallout from 'ee/security_dashboard/components/shared/auto_fix_user_callout.vue';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';

Vue.use(VueApollo);
Vue.use(VueRouter);
const router = new VueRouter();

const examplePipeline = { id: 1, createdAt: 'now', path: 'path' };

describe('Project vulnerability report app component', () => {
  useLocalStorageSpy();

  let wrapper;

  const securityScannersHandler = ({ available = [], enabled = [], pipelineRun = [] } = {}) =>
    jest.fn().mockResolvedValue({
      data: {
        project: {
          id: 1,
          securityScanners: { available, enabled, pipelineRun },
        },
      },
    });

  const createWrapper = ({
    pipeline = examplePipeline,
    sbomPipeline = examplePipeline,
    hasVulnerabilities = true,
    securityScanners,
    securityAutoFix = false,
  } = {}) => {
    wrapper = shallowMount(ProjectVulnerabilityReport, {
      router,
      apolloProvider: createMockApollo([
        [securityScannersQuery, securityScannersHandler(securityScanners)],
      ]),
      provide: {
        fullPath: '#',
        pipeline,
        sbomPipeline,
        hasVulnerabilities,
        dashboardType: DASHBOARD_TYPES.PROJECT,
        glFeatures: { securityAutoFix },
      },
      stubs: {
        VulnerabilityReportTabs,
        VulnerabilityReportTab,
        LocalStorageSync,
      },
    });
  };

  const findReportNotConfiguredProject = () => wrapper.findComponent(ReportNotConfiguredProject);
  const findVulnerabilityReportTabs = () => wrapper.findComponent(VulnerabilityReportTabs);
  const findAutoFixUserCallout = () => wrapper.findComponent(AutoFixUserCallout);
  const findProjectPipelineStatus = () => wrapper.findComponent(ProjectPipelineStatus);
  const findSecurityScannerAlert = (root = wrapper) => root.findComponent(SecurityScannerAlert);

  describe('report not configured component', () => {
    it('shows the not configured component if there is no pipeline and no vulnerabilities', () => {
      createWrapper({ pipeline: {}, hasVulnerabilities: false });

      expect(findReportNotConfiguredProject().exists()).toBe(true);
      expect(findVulnerabilityReportTabs().exists()).toBe(false);
    });

    it.each`
      pipeline           | hasVulnerabilities
      ${examplePipeline} | ${false}
      ${{}}              | ${true}
      ${examplePipeline} | ${true}
    `(
      'shows the report if pipeline is $pipeline and hasVulnerabilities is $hasVulnerabilities',
      ({ pipeline, hasVulnerabilities }) => {
        createWrapper({ pipeline, hasVulnerabilities });

        expect(findReportNotConfiguredProject().exists()).toBe(false);
        expect(findVulnerabilityReportTabs().exists()).toBe(true);
      },
    );
  });

  describe('project pipeline status component', () => {
    it('shows the component if there is a pipeline', () => {
      createWrapper();

      expect(findProjectPipelineStatus().props('pipeline')).toBe(examplePipeline);
    });

    it('does not show the component if there is no pipeline', () => {
      createWrapper({ pipeline: {} });

      expect(findProjectPipelineStatus().exists()).toBe(false);
    });
  });

  describe('security scanner alerts component', () => {
    describe.each`
      available   | enabled     | pipelineRun | expectAlertShown
      ${['DAST']} | ${[]}       | ${[]}       | ${true}
      ${['DAST']} | ${['DAST']} | ${[]}       | ${true}
      ${['DAST']} | ${[]}       | ${['DAST']} | ${true}
      ${['DAST']} | ${['DAST']} | ${['DAST']} | ${false}
      ${[]}       | ${[]}       | ${[]}       | ${false}
    `('visibility', ({ available, enabled, pipelineRun, expectAlertShown }) => {
      it(`should${expectAlertShown ? '' : ' not'} show the alert`, async () => {
        createWrapper({ securityScanners: { available, enabled, pipelineRun } });
        await waitForPromises();

        expect(findSecurityScannerAlert().exists()).toBe(expectAlertShown);
      });

      it('should never show the alert once it has been dismissed', () => {
        window.localStorage.setItem(
          ProjectVulnerabilityReport.SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY,
          'true',
        );

        createWrapper({ securityScanners: { available, enabled, pipelineRun } });

        expect(findSecurityScannerAlert().exists()).toBe(false);
      });
    });

    describe('dismissal', () => {
      beforeEach(async () => {
        createWrapper({
          securityScanners: { available: ['DAST'], enabled: [], pipelineRun: [] },
        });
        await waitForPromises();
      });

      it('should hide the alert when it is dismissed', async () => {
        const scannerAlert = findSecurityScannerAlert();
        expect(scannerAlert.exists()).toBe(true);

        scannerAlert.vm.$emit('dismiss');
        await nextTick();

        expect(scannerAlert.exists()).toBe(false);
      });

      it('should remember the dismissal state', async () => {
        findSecurityScannerAlert().vm.$emit('dismiss');
        await nextTick();

        expect(window.localStorage.setItem).toHaveBeenCalledWith(
          ProjectVulnerabilityReport.SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY,
          'true',
        );
      });
    });

    describe.each(Object.keys(SCANNER_NAMES_MAP))(
      'When %s is available but not enabled',
      (scanner) => {
        const translatedScannerName = SCANNER_NAMES_MAP[scanner];

        beforeEach(async () => {
          createWrapper({
            securityScanners: { available: [scanner], enabled: [], pipelineRun: [] },
          });
          await waitForPromises();
        });

        it(`passes the translated scanner's name to the alert (${translatedScannerName})`, () => {
          expect(findSecurityScannerAlert().props('notEnabledScanners')[0]).toBe(
            translatedScannerName,
          );
        });
      },
    );
  });

  describe('auto fix user callout component', () => {
    it('does not show user callout when feature flag is disabled', () => {
      createWrapper({ securityAutoFix: false });
      expect(findAutoFixUserCallout().exists()).toBe(false);
    });

    it('shows user callout when the cookie is not set and hides it when dismissed', async () => {
      jest.spyOn(Cookies, 'set');
      createWrapper({ securityAutoFix: true });
      const autoFixUserCallOut = findAutoFixUserCallout();

      expect(autoFixUserCallOut.exists()).toBe(true);

      autoFixUserCallOut.vm.$emit('close');
      await nextTick();

      expect(autoFixUserCallOut.exists()).toBe(false);
      expect(Cookies.set).toHaveBeenCalledWith(
        wrapper.vm.$options.autoFixUserCalloutCookieName,
        'true',
        {
          expires: 365,
          secure: false,
        },
      );
    });

    it('does not show user callout when the cookie is set', () => {
      jest.doMock('js-cookie', () => ({ get: jest.fn().mockReturnValue(true) }));
      createWrapper({ securityAutoFix: true });

      expect(findAutoFixUserCallout().exists()).toBe(false);
    });
  });

  describe('security training promotion', () => {
    it('shows user callout', () => {
      createWrapper();

      expect(wrapper.findComponent(SecurityTrainingPromoBanner).exists()).toBe(true);
    });
  });
});
